﻿using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using BasicParts;
using MessageMachine;

namespace DialogMachine
{
    public class DialogManager : ISubscriber
    {
        #region Message Fields
        //Sending messages.
        /// <summary> ExtraInfo is not used.</summary>
        public int msgStoppedDialog;
        /// <summary> ExtraInfo is a Tuple(new line, subtitle settings).</summary>
        public int msgStartedLine;
        /// <summary> ExtraInfo is the started dialog.</summary>
        public int msgStartedDialog;
        /// <summary> ExtraInfo is the file path to the loaded codex.</summary>
        public int msgLoadedCodex;

        //Receiving messsages.
        /// <summary> ExtraInfo is not used.</summary>
        public int msgStopDialog;
        /// <summary> ExtraInfo should be a string with the name of the dialog.</summary>
        public int msgStartDialog;
        /// <summary> ExtraInfo is not used.</summary>
        public int msgStartLine;
        #endregion //Message Fields

        #region General Fields
        /// <summary> The current line.</summary>
        private Line currentLine;
        /// <summary> The index of the current line in the current dialog.</summary>
        private int currentLineIndex;
        /// <summary> The name of the current dialog.</summary>
        private string currentDialog;

        /// <summary> If the DialogService is operating.</summary>
        public bool Active { get; private set; }
        /// <summary> The entire file path to the codex, including file extension.</summary>
        public string codexPath = "";
        /// <summary> All of the loaded dialogs from the codex.</summary>
        private Dictionary<string, Dialog> dialogs = new Dictionary<string, Dialog>();
        /// <summary> For how long this line has dragged on.</summary>
        private int timeTalking;
        /// <summary> All of the subtitle settings.</summary>
        public SubtitleSettings subSettings;
        #endregion //General Fields

        #region Constructors
        /// <summary>
        /// Creates a new DialogService. Use either ParseXML(...) or ParseTXT(...) to
        /// load a file into the service. Loaded files can then be called with the
        /// StartDialog(...) method, followed by calls to Update(...) every frame.
        /// If the constructor w/o the subtitle stuff is used, PrepareForSubtitles(...)
        /// must be called before any use of subtitles.
        /// </summary>
        /// <param name="msgStartDialog">Msg# to start a new dialog.</param>
        /// <param name="msgStopDialog">Msg# to stop a dialog.</param>
        /// <param name="msgStartLine">Msg# to start a new line.</param>
        /// <param name="msgLoadedCodex">Msg# when a codex has loaded.</param>
        /// <param name="msgStartedDialog">Msg# when a dialog has started.</param>
        /// <param name="msgStartedLine">Msg# when a new line has started.</param>
        /// <param name="msgStoppedDialog">Msg# when a dialog has stopped.</param>
        /// <param name="subtitleSettings">All of the settings for subtitles.</param>
        public DialogManager(int msgStartDialog, int msgStopDialog, int msgStartLine,
            int msgLoadedCodex, int msgStartedDialog, int msgStartedLine, int msgStoppedDialog,
            SubtitleSettings subtitleSettings)
        {
            subSettings = subtitleSettings;
            this.msgStartDialog = msgStartDialog;
            this.msgStopDialog = msgStopDialog;
            this.msgStartLine = msgStartLine;
            this.msgLoadedCodex = msgLoadedCodex;
            this.msgStartedDialog = msgStartedDialog;
            this.msgStartedLine = msgStartedLine;
            this.msgStoppedDialog = msgStoppedDialog;
            MessagePasser.Subscribe(new int[] { msgStartDialog, msgStartLine, msgStopDialog }, this);
        }
        /// <summary>
        /// Creates a new DialogService. Use either ParseXML(...) or ParseTXT(...) to
        /// load a file into the service. Loaded files can then be called with the
        /// StartDialog(...) method, followed by calls to Update(...) every frame.
        /// If the constructor w/o the subtitle stuff is used, PrepareForSubtitles(...)
        /// must be called before any use of subtitles.
        /// </summary>
        /// <param name="msgStartDialog">Msg# to start a new dialog.</param>
        /// <param name="msgStopDialog">Msg# to stop a dialog.</param>
        /// <param name="msgStartLine">Msg# to start a new line.</param>
        /// <param name="msgLoadedCodex">Msg# when a codex has loaded.</param>
        /// <param name="msgStartedDialog">Msg# when a dialog has started.</param>
        /// <param name="msgStartedLine">Msg# when a new line has started.</param>
        /// <param name="msgStoppedDialog">Msg# when a dialog has stopped.</param>
        public DialogManager(int msgStartDialog, int msgStopDialog, int msgStartLine,
            int msgLoadedCodex, int msgStartedDialog, int msgStartedLine, int msgStoppedDialog)
        {
            subSettings = new SubtitleSettings() { displaySubs = false };
            this.msgStartDialog = msgStartDialog;
            this.msgStopDialog = msgStopDialog;
            this.msgStartLine = msgStartLine;
            this.msgLoadedCodex = msgLoadedCodex;
            this.msgStartedDialog = msgStartedDialog;
            this.msgStartedLine = msgStartedLine;
            this.msgStoppedDialog = msgStoppedDialog;
            MessagePasser.Subscribe(new int[] { msgStartDialog, msgStartLine, msgStopDialog }, this);
        }
        #endregion //Constructors

        #region Update
        /// <summary> Updates any ongoing dialogs.</summary>
        /// <param name="elapsedTime">How long time has elapsed since the last update.</param>
        public void Update(int elapsedTime)
        {
            if (Active)
            {
                timeTalking += elapsedTime;
                if (timeTalking >= currentLine.duration && currentLine.duration != 0)
                    NextLine();
            }
        }
        #endregion //Update

        #region Line Handling
        /// <summary>
        /// Starts the next line.
        /// </summary>
        private void NextLine()
        {
            timeTalking = 0;
            currentLineIndex++;
            if (dialogs[currentDialog].lines.Count > currentLineIndex)
                DisplayLine(dialogs[currentDialog].lines[currentLineIndex]);
            else
                Stop();
        }

        /// <summary>
        /// Starts a new dialog. If a dialog is already running
        /// the new one overrides it and stops the old one.
        /// </summary>
        /// <param name="name">The name of the dialog.</param>
        /// <returns>False if the dialog doesn't exist, otherwise true.</returns>
        public bool Start(string name)
        {
            if (!dialogs.ContainsKey(name)) return false;
            Stop();
            Active = true;
            currentDialog = name;
            currentLine = dialogs[currentDialog].lines[currentLineIndex];
            DisplayLine(currentLine);
            Send(new Message(msgStartedDialog, dialogs[currentDialog], this));
            return true;
        }

        /// <summary>
        /// Immediately stops the current dialog, reseting all
        /// values and calling AudioHandler.StopVoices().
        /// </summary>
        public void Stop()
        {
            timeTalking = 0;
            currentLineIndex = 0;
            Active = false;
            Send(new Message(msgStoppedDialog));
        }

        /// <summary>
        /// Called when a new line should be shown.
        /// Updates currentLine and plays the voicePath over.
        /// </summary>
        /// <param name="line"></param>
        private void DisplayLine(Line line)
        {
            currentLine = line;
            Send(new Message(msgStartedLine, new Tuple(currentLine, subSettings), this));
        }
        #endregion //Line Handling

        #region Sub Methods
        /// <summary>
        /// Finds the correct position for the subtitles,
        /// making them centred on the screen.
        /// </summary>
        /// <returns></returns>
        public Vector2 FindSubPos()
        {
            return subSettings.FindSubPos(currentLine);
        }
        #endregion //Sub Methods

        #region Parsing
        /// <summary>
        /// Reads an xml file and deserializes it into dialogs.
        /// Also stores the path to the file for later use and/or retrieval.
        /// </summary>
        /// <param name="fileName">The xml file.</param>
        /// <returns>True if successful, otherwise false.</returns>
        public bool ParseXML(string fileName)
        {
            string path = fileName;
            if (!path.EndsWith(".xml")) path += ".xml";
            if (File.Exists(path))
            {
                XmlReader xRead = XmlReader.Create(path);
                dialogs = (Dictionary<string, Dialog>)new XmlSerializer
                    (typeof(Dictionary<string, Dialog>)).Deserialize(xRead);
                codexPath = path;
                Send(new Message(msgLoadedCodex, codexPath, this));
                return true;
            }
            return false;
        }

        /// <summary>
        /// Parses a txt document into an xml document,
        /// with full Dialog support and so on.
        /// The name of the xml document will be the same
        /// as the txt document, only using .xml instead of .txt.
        /// Also loads this file.
        /// </summary>
        /// <param name="fileName">The name and path of the file.</param>
        public void ParseTXT(string fileName)
        {
            dialogs.Clear();
            StreamReader sr = new StreamReader(fileName);
            Dialog dia = new Dialog();
            string scene = "";
            string speaker = "";
            string thisLine = "";
            string voice = "";
            int duration = 0;
            while (!sr.EndOfStream)
            {
                thisLine = sr.ReadLine();
                //Checks if it is at the start of a new scene,
                //and if so adds the previous scene to dialogs.
                if (thisLine.StartsWith("#"))
                {
                    if (dia.lines.Count > 0)
                    {
                        if (dialogs.ContainsKey(scene))
                            dialogs.Remove(scene);
                        dialogs.Add(scene, dia);
                    }
                    dia = new Dialog();
                    scene = thisLine.TrimStart('#');
                }
                //Gets the speakerName.
                else if (thisLine.StartsWith("="))
                    speaker = thisLine.TrimStart('=');
                //Gets the voiceover.
                else if (thisLine.StartsWith("["))
                    voice = thisLine.TrimStart('[');
                else if (thisLine.StartsWith("@"))
                    int.TryParse(thisLine.TrimStart('@'), out duration);
                //Comments.
                else if (thisLine.StartsWith("!")) { }
                //Gets the line and adds it, along with the speakerName,
                //to the current dialog.
                else if (thisLine.Length != 0)
                {
                    //Check for multiple lines of text
                    int next = sr.Peek();
                    //The numbers are the ascii codes for the special characters.
                    if (next > 33 && next != 35 && next != 91 && next != 61 && next != 64)
                        thisLine += "\n" + sr.ReadLine();

                    dia.lines.Add(new Line(thisLine, speaker, voice, duration));
                }
            }
            //Adds the last scene.
            if (dialogs.ContainsKey(scene))
                dialogs.Remove(scene);
            dialogs.Add(scene, dia);

            //Writes to the xml file.
            codexPath = fileName.Remove(fileName.Length - 3) + "xml";
            XmlWriter xWrite = XmlWriter.Create(codexPath);
            xWrite.Settings.Indent = true;
            new XmlSerializer(typeof(Dictionary<string, Dialog>)).Serialize(xWrite, dialogs);
            xWrite.Close();

            Send(new Message(msgLoadedCodex, codexPath, this));
        }
        #endregion //Parsing

        #region ISubscriber Members
        public void Receive(Message msg)
        {
            if (msg.msgType == msgStartDialog)
                Start(msg.extraInfo.ToString());
            else if (msg.msgType == msgStopDialog)
                Stop();
            else if (msg.msgType == msgStartLine)
                NextLine();
        }
        #endregion
        private void Send(Message msg)
        {
            MessagePasser.Send(msg);
        }
    }
}